perf(guest): read the private input zero-copy via ef_io::read_input - #886
Merged
Conversation
get_private_input() to_vec()'s the whole memory-mapped input before rkyv deserializes it; read_input hands rkyv a slice straight into the input region instead. Same bytes, same private-input commitment. Measured vs origin/main (same fixtures, deterministic): transfers_20 8,732,213 -> 8,692,490 (-39,723) erc20_20 10,328,222 -> 10,278,822 (-49,400) mixed_20 9,817,444 -> 9,768,492 (-48,952) Verified: test_prove_ethrex_empty_block (prove+verify) passes.
Collaborator
Author
|
/bench |
Benchmark — real block (
|
| Metric | main | PR | Δ |
|---|---|---|---|
| Peak heap | 48519 MB | 45915 MB | -2604 MB (-5.4%) 🟢 |
| Prove time | 166.381s | 158.246s | -8.135s (-4.9%) ⚪ |
❓ -4.9% — beyond what 3 runs resolve. Use
/bench-abbafor a paired test of the same block (default 12 pairs, ~72 min, resolves ~1%).Prove-time spread 1.2% (158.246s / 159.049s / 157.171s)
Commit: a6ca32e · Baseline: cached · Runner: self-hosted bench
Collaborator
Author
|
/bench |
Collaborator
Author
|
/bench |
diegokingston
marked this pull request as ready for review
August 3, 2026 14:51
Contributor
|
/bench |
…lice (#898) The zero-copy read is the right call, but it hand-rolls what `syscalls::get_private_input_slice` already does: borrow the mapped private-input region in place and hand back `&'static [u8]`, no copy and no allocation. `get_private_input` is that same call plus a `to_vec()`, so dropping to the slice is the whole win without the pointer plumbing. Three things that buys: - No raw pointers in guest code. `syscalls.rs` deliberately keeps the region layout and its one `unsafe` block in a single place — that is why `get_private_input_slice` exists. Re-reading the length prefix in the guest duplicates layout knowledge that has to stay in step with the executor. - Restores the length-prefix clamp. `get_private_input_slice` bounds the prefix by `MAX_PRIVATE_INPUT_SIZE`; `ef_io::read_input` returns it raw. The executor rejects oversized inputs, so honest runs are identical — but a forged prefix built a slice reaching past the region instead of a bounded one. - Drops a dependency on unspecified behavior. `ef_io::read_input` documents `buf_ptr` as unspecified when `buf_size == 0`, and the previous code fed it to `from_raw_parts` regardless. Harmless in practice (the implementation always writes it, and ethrex input is never empty), but not a contract to lean on. `bench_vs/lambda/recursion` already reads its blob this way.
MauroToscano
approved these changes
Aug 4, 2026
MauroToscano
enabled auto-merge
August 4, 2026 19:29
MauroToscano
disabled auto-merge
August 4, 2026 19:36
Contributor
|
/bench |
MauroToscano
enabled auto-merge
August 4, 2026 19:42
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
get_private_input() to_vec()'s the whole memory-mapped input before rkyv deserializes it; read_input hands rkyv a slice straight into the input region instead. Same bytes, same private-input commitment.
Measured vs origin/main (same fixtures, deterministic):
transfers_20 8,732,213 -> 8,692,490 (-39,723)
erc20_20 10,328,222 -> 10,278,822 (-49,400)
mixed_20 9,817,444 -> 9,768,492 (-48,952)
Verified: test_prove_ethrex_empty_block (prove+verify) passes.